package pl.com.qapps.datastore.berkeley.impl;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import pl.com.qapps.datastore.base.TransactionStack;

import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.utils.FutureWrapper;
import com.googlecode.objectify.util.FutureHelper;

final class TransactionImpl implements Transaction {

	enum TransactionState {
		BEGUN, COMPLETION_IN_PROGRESS, COMMITTED, ROLLED_BACK, ERROR
	}

	/**
	 * A {@link PostOpFuture} implementation that runs both post put and post
	 * delete callbacks.
	 */
	// private class PostCommitFuture extends PostOpFuture<Void> {
	// private final List<Entity> putEntities;
	// private final List<Key> deletedKeys;
	//
	// private PostCommitFuture(List<Entity> putEntities,
	// List<Key> deletedKeys, Future<Void> delegate) {
	// super(delegate, callbacks);
	// this.putEntities = putEntities;
	// this.deletedKeys = deletedKeys;
	// }
	//
	// @Override
	// void executeCallbacks() {
	// PutContext putContext = new PutContext(TransactionImpl.this,
	// putEntities);
	// callbacks.executePostPutCallbacks(putContext);
	// DeleteContext deleteContext = new DeleteContext(
	// TransactionImpl.this, deletedKeys);
	// callbacks.executePostDeleteCallbacks(deleteContext);
	// }
	// }

	private final TransactionStack txnStack;

	TransactionState state = TransactionState.BEGUN;
	final com.sleepycat.je.Transaction raw;
	final ExecutorService dispatcher;

	TransactionImpl(TransactionStack txnStack,
			com.sleepycat.je.Transaction rawTxn, ExecutorService service) {
		this.txnStack = txnStack;
		this.raw = rawTxn;
		this.dispatcher = service;
	}

	/**
	 * Provides the unique identifier for the txn. Blocks on the future since
	 * the handle comes back from the datastore server.
	 */
	private long getHandle() {
		return raw.getId();
	}

	@Override
	public void commit() {
		FutureHelper.quietGet(commitAsync());
	}

	public Future<Void> commitAsync() {
		ensureTxnStarted();
		state = TransactionState.COMPLETION_IN_PROGRESS;
		try {
			for (Future<?> f : txnStack.getFutures(this)) {
				FutureHelper.quietGet(f);
			}
			return new FutureWrapper<Void, Void>(
					dispatcher.submit(new Callable<Void>() {

						@Override
						public Void call() throws Exception {
							raw.commit();
							return null;
						}

					})) {
				@Override
				protected Void wrap(Void ignore) throws Exception {
					state = TransactionState.COMMITTED;
					return null;
				}

				@Override
				protected Throwable convertException(Throwable cause) {
					state = TransactionState.ERROR;
					return cause;
				}
			};
		} finally {
			txnStack.remove(this);
		}
	}

	@Override
	public void rollback() {
		FutureHelper.quietGet(rollbackAsync());
	}

	public Future<Void> rollbackAsync() {
		ensureTxnStarted();
		state = TransactionState.COMPLETION_IN_PROGRESS;
		try {
			for (Future<?> f : txnStack.getFutures(this)) {
				FutureHelper.quietGet(f);
			}
			return new FutureWrapper<Void, Void>(
					dispatcher.submit(new Callable<Void>() {

						@Override
						public Void call() throws Exception {
							raw.abort();
							return null;
						}

					})) {
				@Override
				protected Void wrap(Void ignore) throws Exception {
					state = TransactionState.ROLLED_BACK;
					return null;
				}

				@Override
				protected Throwable convertException(Throwable cause) {
					state = TransactionState.ERROR;
					return cause;
				}
			};
		} finally {
			txnStack.remove(this);
		}
	}

	@Override
	public String getApp() {
		return "unsupported";
	}

	@Override
	public String getId() {
		return Long.toString(getHandle());
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}

		TransactionImpl that = (TransactionImpl) o;

		return getHandle() == ((TransactionImpl) that).getHandle();
	}

	@Override
	public int hashCode() {
		return (int) (getHandle() ^ (getHandle() >>> 32));
	}

	@Override
	public String toString() {
		return "Txn [" + getHandle() + ", " + state + "]";
	}

	@Override
	public boolean isActive() {
		return state == TransactionState.BEGUN
				|| state == TransactionState.COMPLETION_IN_PROGRESS;
	}

	/**
	 * If {@code txn} is not null and not active, throw
	 * {@link IllegalStateException}.
	 * @deprecated now in ApiUtils
	 */
	static void ensureTxnActive(Transaction txn) {
		if (txn != null && !txn.isActive()) {
			throw new IllegalStateException(
					"Transaction with which this operation is "
							+ "associated is not active.");
		}
	}

	private void ensureTxnStarted() {
		if (state != TransactionState.BEGUN) {
			throw new IllegalStateException("Transaction is in state " + state
					+ ".  There is no legal " + "transition out of this state.");
		}
	}
}
